/*
 * ConfigurationManagerDialog.cpp
 *
 *  Created on: Oct 28, 2012
 *      Author: johnny
 */

#include "ConfigurationManagerDialog.h"

#include <list>
#include <map>
#include <QPushButton>
#include <QPainter>

#include "../Engine.h"
#include "../ConfigurationManager.h"
#include "../MeasConnectionHandler.h"

namespace ui {

unsigned short ConfigurationManagerDialog::ANIMATION_DURATION = 300;

ConfigurationManagerDialog::ConfigurationManagerDialog(QWidget* _parent): BaseUI(), animation(NULL),
    moSelectWidget(NULL), moveHandler(NULL), parentAnimating(false), mainWidgetWidth(0), mainWidgetHeight(0),
    minX(0), minY(0), maxX(0), maxY(0), isSetUp(false), nodesMappedOk(true), linesDrawn(true) {

    parent = _parent;
}

ConfigurationManagerDialog::~ConfigurationManagerDialog() {
    if(moSelectWidget != NULL)
        delete moSelectWidget;

    if(animation != NULL)
        delete animation;

    if(moveHandler != NULL)
        delete moveHandler;

    clearNodes();
}

bool ConfigurationManagerDialog::setup() {
    if(isSetUp) {
        linesDrawn = false;
        return true;
    }

    if(!BaseUI::setup(UI_CFG_MANAGER_GRAPH))
        return false;

    formWidget->setWindowFlags(Qt::Dialog);

    stackedWidget               = formWidget->findChild<QStackedWidget*>("stackedWidget");
    lblDetails                  = formWidget->findChild<QLabel*>("lblDetails");
    pageMOSelect                = formWidget->findChild<QWidget*>("pageMOSelect");
    pageGraph                   = formWidget->findChild<QWidget*>("pageGraph");
    mainWidget                  = formWidget->findChild<QWidget*>("mainWidget");
    unusedWidget                = formWidget->findChild<QWidget*>("unusedWidget");

    lblDetails->setText("");

    moSelectWidget = new MOSelectionWidget(pageMOSelect);
    if(!moSelectWidget->setup())
        return false;

    moSelectWidget->populateLists();

    stackedWidget->insertWidget(1, moSelectWidget->getWidget());

    QPushButton *btnAddMO           = formWidget->findChild<QPushButton*>("btnAddMO");
    QPushButton *btnRemoveMO        = formWidget->findChild<QPushButton*>("btnRemoveMO");
    QPushButton *btnRefresh         = formWidget->findChild<QPushButton*>("btnRefresh");
    QPushButton *btnMOSelectOK      = stackedWidget->findChild<QPushButton*>("btnOk");
    QPushButton *btnMOSelectCancel  = stackedWidget->findChild<QPushButton*>("btnCancel");

    QObject::connect(btnAddMO, SIGNAL(clicked()), this, SLOT(onAddMo()));
    QObject::connect(btnRemoveMO, SIGNAL(clicked()), this, SLOT(onRemoveMo()));
    QObject::connect(btnRefresh, SIGNAL(clicked()), this, SLOT(onRefresh()));
    QObject::connect(btnMOSelectOK, SIGNAL(clicked()), this, SLOT(onMOSelectOK()));
    QObject::connect(btnMOSelectCancel, SIGNAL(clicked()), this, SLOT(onMOSelectCancel()));

    stackedWidget->setCurrentIndex(0); // Cfg page

    moveHandler = new MovementHandler(mainWidget, *this);
    mainWidget->installEventFilter(moveHandler);
    formWidget->installEventFilter(moveHandler);

    isSetUp = true;
    return true;
}

void ConfigurationManagerDialog::onAddMo() {
    parentAnimating = true;

    animation = new QPropertyAnimation(pageGraph, "geometry", stackedWidget);
    animation->setEasingCurve(QEasingCurve::InQuad);
    animation->setDuration(ANIMATION_DURATION);
    animation->setStartValue(stackedWidget->rect());
    animation->setEndValue(QRect(0, 0, 0, 0));

    QObject::connect(animation, SIGNAL(finished()), this, SLOT(onGraphAnimOut()));
    animation->start();
}

void ConfigurationManagerDialog::onRemoveMo() {
    if(moveHandler->selectedNode) {
        for(std::list<GraphNode*>::iterator it = graphNodes.begin();
            it != graphNodes.end(); ++it) {

            if((*it)->getWidget() == moveHandler->selectedNode) {
                core::Engine::getInstance().removeMOByMeasIndex((*it)->getMeasIndex());
                moveHandler->selectedNode = NULL;

                delete *it;
                graphNodes.remove(*it);

                mainWidget->update();
                return;
            }
        }
    }
}

void ConfigurationManagerDialog::onRefresh() {
    autoArrangeMOs();

    linesDrawn = false;
    mainWidget->update();
}

void ConfigurationManagerDialog::onMOSelectOK() {
    IndexLst selected = moSelectWidget->getSelected();

    // use current shown widget to get frame size
    mainWidgetWidth = moSelectWidget->getWidget()->width();
    mainWidgetHeight = moSelectWidget->getWidget()->height();

    if(!selected.empty()) {
        for(IndexLst::const_iterator it = selected.begin();
            it != selected.end(); ++it) {

            core::Engine::getInstance().loadMOByIndex(*it);
        }

        autoArrangeMOs();
        drawLines();
    }

    stackedWidget->setCurrentIndex(0);
}

void ConfigurationManagerDialog::onMOSelectCancel() {
    stackedWidget->setCurrentIndex(0);
}

void ConfigurationManagerDialog::onGraphAnimOut() {
    delete animation;
    animation = NULL;

    parentAnimating = false;
    stackedWidget->setCurrentIndex(1);
}

void ConfigurationManagerDialog::autoArrangeMOs(bool keep) { //todo: animate each node to its position
    const core::MeasBaseStorage& loaded = core::ConfigurationManager::getInstance().getLoadedMOs();
    boost::shared_ptr<measurement::MeasurementObject> mo;
    std::list<core::MeasConnection*> connections =
            core::ConfigurationManager::getInstance().getMeasConnHandler().getConnections();

    std::list<QPoint> positions;

    GraphNode* node;
    QString currentType;

    if(!keep) {
        // initial cleanup
        nodesMappedOk = false;
        linesDrawn = false;
        clearNodes();
        indexedNodes.clear();
    }

    // initial arange
    int x = 0;

    // create
    for(core::MeasBaseStorage::const_iterator it = loaded.begin();
        it != loaded.end(); ++it) {

        if(indexedNodes[(*it)->getMeasIndex()]) // graph node exists
            continue;

        mo = (*it)->getMOLink();

        switch(mo->type) {
        case measurement::DataSource:
            currentType = MO_CATEGORY_DATA_SOURCE;
            break;
        case measurement::DataProcessor:
            currentType = MO_CATEGORY_DATA_PROCESSOR;
            break;
        case measurement::Visualization:
            currentType = MO_CATEGORY_VISUALIZATION;
            break;
        }

        // may be problematic if node should go to unused widget
        node = new GraphNode(mainWidget);
        if(!node->setup()) {
            core::Logger::getInstance().add(core::Error,
                "ConfigurationManagerDialog::displayLoadedMOs (Error creating display)");
            return;
        }

        node->setName(QString::fromStdWString(mo->name));
        node->setHasInput(mo->inputType != L"");
        node->setHasOutput(mo->outputType != L"");
        node->setType(currentType);
        node->setMeasIndex((*it)->getMeasIndex());
        node->setTempPos(x++, 0);
        node->setBaseLink(core::WeakMOPtr(*it));

        indexedNodes[(*it)->getMeasIndex()] = node;
        graphNodes.push_back(node);
    }

    // re-arange
    for(std::list<core::MeasConnection*>::iterator connIter = connections.begin();
        connIter != connections.end(); ++connIter) {

        core::SharedMOPtr from  = (*connIter)->first.lock();
        core::SharedMOPtr to    = (*connIter)->second.lock();

        if(!from || !to) {
            core::Logger::getInstance().add(core::Error,
                        "ConfigurationManagerDialog::autoArrangeMOs() error accesing connections");
            return;
        }

        QPoint tempPos = *(indexedNodes[from->getMeasIndex()]->getTempPos());
        GraphNode *currentNode = indexedNodes[to->getMeasIndex()];

        indexedNodes[from->getMeasIndex()]->isTempSet = true;
        currentNode->isTempSet = true;

        for(int y = tempPos.y(); ; ++y) {
            tempPos.setY(tempPos.y() + 1);
            if(tempPos.y() != 0 && std::find(positions.begin(), positions.end(), tempPos) == positions.end()) {
                currentNode->setTempPos(tempPos.x(), tempPos.y());
                positions.push_back(tempPos);
                if(tempPos.x() < minX)
                    minX = tempPos.x();
                if(tempPos.x() > maxX)
                    maxX = tempPos.x();
                if(tempPos.y() < minY)
                    minY = tempPos.y();
                if(tempPos.y() > maxY)
                    maxY = tempPos.y();
                break;
            }
            else {
                tempPos.setY(tempPos.y() - 2);
                if(tempPos.y() != 0 && std::find(positions.begin(), positions.end(), tempPos) == positions.end()) {
                    currentNode->setTempPos(tempPos.x(), tempPos.y());
                    positions.push_back(tempPos);
                    if(tempPos.x() < minX)
                        minX = tempPos.x();
                    if(tempPos.x() > maxX)
                        maxX = tempPos.x();
                    if(tempPos.y() < minY)
                        minY = tempPos.y();
                    if(tempPos.y() > maxY)
                        maxY = tempPos.y();
                    break;
                }
            }
        }
    }

    mapNodes();
}

void ConfigurationManagerDialog::mapNodes() {
    int diffX = maxX - minX;
    int diffY = maxY - minY;

    int pcsX = diffX > 0? mainWidgetWidth / diffX: mainWidgetWidth;
    int pcsY = diffY > 0? mainWidgetHeight / diffY: mainWidgetHeight;

    if(pcsX == 0 || pcsY == 0) {
        nodesMappedOk = false;
        return;
    }

    int xMap = minX * -1;
    int yMap = minY * -1;

    int unusedX = 0;

    QPoint *tempPos;
    for(std::list<GraphNode*>::iterator it = graphNodes.begin();
        it != graphNodes.end(); ++it) {

        QWidget *widget = (*it)->getWidget();
        QRect geom = widget->geometry();

        if(!(*it)->isTempSet) {
            widget->setParent(unusedWidget);

            geom.setX(unusedX);
            geom.setY(0);
            geom.setWidth(widget->minimumWidth());
            geom.setHeight(widget->minimumHeight());

            unusedX += widget->minimumWidth() + 10;
        } else {
            tempPos = (*it)->getTempPos();

            int W = pcsX > widget->minimumWidth()? widget->minimumWidth(): pcsX;
            int H = pcsY > widget->minimumHeight()? widget->minimumHeight(): pcsY;

            geom.setX((tempPos->x() + xMap) * (W + 10));
            geom.setY((tempPos->y() + yMap) * (H + 10));
            geom.setWidth(W);
            geom.setHeight(H);
        }

        widget->setGeometry(geom);
    }

    nodesMappedOk = true;
    linesDrawn = false;
}

void ConfigurationManagerDialog::drawLines() {
    if(linesDrawn)
        return;

    std::list<core::MeasConnection*> connections =
            core::ConfigurationManager::getInstance().getMeasConnHandler().getConnections();

    int r = 3; // circle
    QPainter painter(mainWidget);
    QPen blackPen(Qt::black, 2);
    QPen redPen(Qt::red, 2);
    painter.setRenderHint(QPainter::Antialiasing, true);
    painter.setPen(blackPen);

    bool colorReset = false;

    for(std::list<core::MeasConnection*>::iterator connIter = connections.begin();
        connIter != connections.end(); ++connIter) {

        core::SharedMOPtr from  = (*connIter)->first.lock();
        core::SharedMOPtr to    = (*connIter)->second.lock();

        if(!from || !to) {
            core::Logger::getInstance().add(core::Error,
                        "ConfigurationManagerDialog::drawLines() error accesing connections");
            return;
        }

        QRect geom1 = indexedNodes[from->getMeasIndex()]->getWidget()->geometry();
        QRect geom2 = indexedNodes[to->getMeasIndex()]->getWidget()->geometry();

        QPoint fromPos, toPos;

        if((geom1.x() + geom1.width() + 5) < geom2.x()) { // to the right
            fromPos.setX(geom1.x() + geom1.width());
            fromPos.setY(geom1.y() + geom2.height() / 2);

            toPos.setX(geom2.x());
            toPos.setY(geom2.y() + geom2.height() / 2);
        } else if((geom2.x() + geom2.width() + 5) < geom1.x()) { // to the left
            fromPos.setX(geom1.x());
            fromPos.setY(geom1.y() + geom2.height() / 2);

            toPos.setX(geom2.x() + geom2.width());
            toPos.setY(geom2.y() + geom2.height() / 2);
        } else { // same distance
            if(geom1.y() < geom2.y()) { // down
                fromPos.setX(geom1.x() + geom1.width() / 2);
                fromPos.setY(geom1.y() + geom1.height());

                toPos.setX(geom2.x() + geom2.width() / 2);
                toPos.setY(geom2.y());
            } else { // up
                fromPos.setX(geom1.x() + geom1.width() / 2);
                fromPos.setY(geom1.y());

                toPos.setX(geom2.x() + geom2.width() / 2);
                toPos.setY(geom2.y() + geom2.height());
            }
        }

        if(moveHandler->selectedNode != NULL) {
            if(moveHandler->selectedNodeIndex == from->getMeasIndex() ||
                    moveHandler->selectedNodeIndex == to->getMeasIndex()) {
                painter.setPen(redPen);
                colorReset = true;
            }
        }

        painter.drawLine(fromPos.x(), fromPos.y(), toPos.x(), toPos.y());
        painter.drawEllipse(fromPos, r, r);
        painter.drawEllipse(toPos, r, r);

        if(colorReset)
            painter.setPen(blackPen);
    }

    linesDrawn = true;
}

void ConfigurationManagerDialog::markSelected(QWidget *widget) {
    QRect marking = widget->geometry();
    QPainter painter(mainWidget);
    painter.setPen(QPen(Qt::red, 2));

    painter.drawRect(marking);
}

void ConfigurationManagerDialog::clearNodes() {
    if(!graphNodes.empty()) {
        for(std::list<GraphNode*>::iterator it = graphNodes.begin();
            it != graphNodes.end(); ++it) {

            delete *it;
        }

        graphNodes.clear();
    }
}

void ConfigurationManagerDialog::displayDetails() {
    if(!moveHandler->selectedNode)
        return;

    unsigned short index = moveHandler->selectedNodeIndex;

    core::SharedMOPtr selectedMO(indexedNodes[index]->getBaseLink().lock());

    std::wstring details = L"Selected node:\n\n";
    details += selectedMO->getMOLink()->name;
    details += L"\n\nConnections:\n\n";

    core::ConnectionsList &connections =
            core::ConfigurationManager::getInstance().getMeasConnHandler().getConnections();

    core::SharedMOPtr ptr;
    bool foundConnection;

    for(core::ConnectionsList::const_iterator it = connections.begin();
        it != connections.end(); ++it) {
        foundConnection = false;

        core::SharedMOPtr from = (*it)->first.lock();
        core::SharedMOPtr to = (*it)->second.lock();

        if(!from || !to) return;

        if(from->getMeasIndex() == index) {
            ptr = to;
            foundConnection = true;
        }
        else if(to->getMeasIndex() == index) {
            ptr = from;
            foundConnection = true;
        }

        if(foundConnection) {
            details += L"\t- ";
            details += ptr->getMOLink()->name;
            details += L"\n";
        }
    }

    lblDetails->setText(QString::fromStdWString(details));
}

void ConfigurationManagerDialog::clearDetails() {
    lblDetails->setText("");
}

std::list<GraphNode*>& ConfigurationManagerDialog::nodes() {
    return graphNodes;
}

} /* namespace ui */
